package ownserver;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.logging.Logger;

import pathfinding.GridCell;
import pathfinding.GridGeometry;
import pathfinding.Reservation;
import pathfinding.SpaceTimeReservationTable;
import robotutils.data.DoubleUtils;

import base.Geometry;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.util.ScalableList;

public class MoveTask 
   implements Task,
              ManagedObject,
              Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/** The {@link Logger} for this class. */
    private static final Logger logger =
        Logger.getLogger(MoveTask.class.getName());
	
	ManagedReference<ScalableList<ManagedReference<ServerShip>>> shipsRef;
	ManagedReference<SpaceTimeReservationTable> tableRef;
	
	public MoveTask(ManagedReference<SpaceTimeReservationTable> tableRef) {
		DataManager dataManager = AppContext.getDataManager();
		dataManager.markForUpdate(this);
		
		ScalableList<ManagedReference<ServerShip>> ships = new ScalableList<ManagedReference<ServerShip>> ();
		
		this.shipsRef = dataManager.createReference(ships);
		this.tableRef = tableRef;
	}
	
	/** The time to wait before repeating the task. */
    public static final int PERIOD_MS = 50;
    
    /**  The timestamp when this task was last run. */
    private long lastTimestamp = System.currentTimeMillis();
	
	@Override
	public void run() throws Exception {
		long timestamp = System.currentTimeMillis();
        long delta = timestamp - lastTimestamp;
        
        lastTimestamp = timestamp;
        
        double dt = ((double)delta) / 1000;
        
        ScalableList<Collision> collisions = new ScalableList<Collision>();
        //ScalableList<ServerShip> gridChanged = new ScalableList<ServerShip>();
         
        ScalableList<ManagedReference<ServerShip>> ships = shipsRef.get();
        
        Collision c;
        ServerShip s;
        DataManager dataManager = AppContext.getDataManager();
        
        boolean placeChanged, headingChanged, velChanged;
        
        for (ManagedReference<ServerShip> shipRef : ships) {
    		placeChanged = false; headingChanged = false; velChanged = false;
    		//s = shipRef.get();
    		
    		GridCell old = GridGeometry.getCell(shipRef.get());
    		velChanged = updateVelocity(shipRef, dt);
    		headingChanged = rotateShip(shipRef, dt);
    		placeChanged   = moveShip(shipRef, dt);
    		
    		if (!headingChanged && !placeChanged && !velChanged) {
    			// neither heading nor place nor velocity changed, so continue with next ship
    			continue;
    		}

    		// mark ship in need for updating the grid information for path finding if necessary
    		/*
    		if (GridGeometry.getCell(s) != old) {
    			gridChanged.add(s);
    		}
    		*/

    		/*
    		for (ServerShip colliding : getCollidingShips(s, ships))
    		{
    			// add all collisions that haven't been done before
    			// TODO: the whole collision detection is atm very poorly done, because we're 
    			// probably changing to a physics engine
    			c = new Collision(dataManager.createReference(s), dataManager.createReference(colliding));
    			if (collisions.contains(c) == false)
    			{
    				collisions.add(c);
    			}
    		}
    		*/
    		// TODO: informing the player could be separated into a task of it's own, that wouldn't be run as often
    		informPlayer(shipRef);
    	}
    	/*
        World world = worldRef.get();
    	
    	if (collisions.isEmpty() == false)
    	{
    		// inform world of collisions
    		world.informCollisions(collisions, timestamp);
    	}
    	*/
    	
    	// TODO: we should be able to give the whole list over to the reservation table
    	// so it only needs to call markForUpdate once after it has dealt with the whole table
    
        /*
        SpaceTimeReservationTable table = tableRef.getForUpdate();
    	for (ServerShip changed: gridChanged) {
    		table.updateReservations(dataManager.createReference(changed));
    	}
    	*/
    	
	}
	
	public void addShip(ServerShip ship)
	{
		DataManager dataManager = AppContext.getDataManager();
		shipsRef.getForUpdate().add(dataManager.createReference(ship));
	}
	
	protected ScalableList <ServerShip> getCollidingShips(ServerShip s, ScalableList<ManagedReference<ServerShip>> allShips) {
		ScalableList <ServerShip> result = new ScalableList <ServerShip>();
		ServerShip other;
		for(ManagedReference<ServerShip> otherRef : allShips) {
			other = otherRef.get();
			if (other == s) {
				continue;
			}
			if (s.collides(other)) {
				result.add(other);
			}
		}
		return result;
	}
	
	/**
	 * Rotates the given ship with the amount of time given.
	 * 
	 * @param ship
	 * @param dt
	 * @return true iff the ship's location changed (ie. ship rotated)
	 */
	protected boolean rotateShip(ManagedReference<ServerShip> shipRef, double dt)
	{
		double rotation = shipRef.get().getRotation(); 
		if (rotation != 0)
		{
			ServerShip ship = shipRef.getForUpdate();
			double heading = ship.getHeading() + dt * rotation;
			ship.setHeading(heading);
			return true;
		}
		return false;
	}
	
	protected boolean updateVelocity(ManagedReference<ServerShip> shipRef, double dt)
	{
		double acceleration = shipRef.get().getAcceleration(); 
		if (acceleration != 0)
		{
			ServerShip ship = shipRef.getForUpdate();
			double oldVelocity = ship.getVelocity();
			
			// following min and max ensures velocity stays between minVelocity and maxVelocity
			double newVelocity = Math.min(ship.getMaxVelocity(), oldVelocity + dt * acceleration);
			newVelocity = Math.max(ship.getMinVelocity(), newVelocity);
			if (DoubleUtils.equals(oldVelocity, newVelocity) == false) {
				ship.setVelocity(newVelocity);
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 
	 * @param ship
	 * @param dt
	 * @return true iff the ship's location changed
	 */
	protected boolean moveShip(ManagedReference<ServerShip> shipRef, double dt)
	{
		double velocity = shipRef.get().getVelocity(); 
		if (velocity != 0)
		{
			ServerShip ship = shipRef.getForUpdate();
			double heading = ship.getHeading();
			double dx = dt * velocity * Geometry.getX(heading);
			double dy = dt * velocity * Geometry.getY(heading);
			//logger.log(Level.INFO, "Delta: ({0},{1}) ", new Object[] { dx, dy});
			ship.setX(ship.getX() + dx);
			ship.setY(ship.getY() + dy);
			return true;
		}
		return false;
	}
	
	protected void informPlayer(ManagedReference<ServerShip> shipRef) {
		/*
		Player owner = ship.getOwner();
		//logger.log(Level.INFO, "Informing player: {0}", owner);
		ClientSession session = owner.getSession();
		if (session != null) {
			ByteBuffer buffer = Messages.createShipMovePkt(ship);
			session.send(buffer);
		}
		*/
		ServerShip ship = shipRef.get();
		ByteBuffer buffer = Messages.createShipMovePkt(ship);
		ship.getPublicChannel().send(buffer);
	}
}
